﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SQLite;
using System.Threading;
using Crawler.DataServices.Persistence.nHibernate;
using log4net;
using NHibernate;

namespace Crawler.DataServices.Persistence.UnitOfWork
{
	internal class UnitOfWork : IUnitOfWork
	{
		protected readonly ILog Logger = LogManager.GetLogger("AppLogger");

		private  ISession _session;
		private  ITransaction _transaction;
		private readonly ISessionFactoryProvider _sessionFactoryProvider;
		private readonly bool _autoCommitWhenDispose;

		private readonly Guid _id = Guid.NewGuid();
		public UnitOfWork(ISessionFactoryProvider sessionFactoryProvider)
		{
			_sessionFactoryProvider = sessionFactoryProvider;
			_autoCommitWhenDispose = false;
			
		}

		public void SetToWorkWithContent()
		{
					if(_session!=null) throw new InvalidOperationException("Already set");
			workwithContent = true;
		}

		private bool workwithContent;

		~UnitOfWork()
		{
			Dispose(false);
		}
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (_autoCommitWhenDispose)
					Commit(true);
				else
				{
					if (_transaction != null)
						_transaction.Dispose();
				}
				_session.Connection.Close();
				_session.Dispose();
			}
			else
			{
				if (_autoCommitWhenDispose && IsInActiveTransaction)
				{
					Logger.ErrorFormat("Despite UoW is configured to autocommit when disposed was never commited. - Id: {1}", _id);
				}
			}
		}


		public void Commit(bool disposing)
		{
			if (_autoCommitWhenDispose && !disposing)
				throw new InvalidOperationException(
					"The unit of work is configured to autocommit when dispose, Commit cannot be requested by caller as is automatic");

			if (_session==null || _transaction == null)
				return;

			try
			{
				_session.Flush();
				_transaction.Commit();
			}
			catch (Exception ex)
			{
				Logger.ErrorFormat("RollingBack. Disposing={0}, thread={1} - Id: {2} - Exception:{3}", disposing, Thread.CurrentThread.ManagedThreadId, _id, ex.ToString());
				_transaction.Rollback();
				throw;
			}
			finally
			{
				_transaction.Dispose();
			}
		}

		public void Commit()
		{
			Commit(false);
		}

		public bool IsReadOnly { get { return _session.FlushMode == FlushMode.Never; } }

		private bool IsInActiveTransaction
		{
			get { return _session!=null && _session.Transaction != null && _session.Transaction.IsActive; }
		}

		private readonly object _lock = new object();
		public ISession Session
		{
			get
			{
				if (_session == null)
					lock (_lock)
						if (_session == null)
						{
							_session = SessionFactory.OpenSession();
							var trCreated = false;
							int tries = 30;
							do
							{
								try
								{
									_transaction = _session.BeginTransaction();
									trCreated = true;
								}
								catch (TransactionException te)
								{
									if (te.InnerException == null || (te.InnerException.GetType() != typeof (SQLiteException)&&  te.InnerException.GetType() != typeof (SqlException)))
										throw;
									Thread.Sleep(TimeSpan.FromSeconds(1));
									tries--;
								}
							} while (!trCreated);
						}

				return _session;
			}
		}

		private ISessionFactory SessionFactory
		{
			get
			{
				return workwithContent
					? _sessionFactoryProvider.CrawlingSessionFactory
					: _sessionFactoryProvider.MoviesSessionFactory;
			}
		}


	}

}
